#include "enemy.hpp"

ActPool<Enemy, 14> Enmes;

static const Vec2f SIZES[Enemy::TYPECT] = 
{
	Vec2f(2, 2),
	Vec2f(2, 2),	Vec2f(2, 2),	Vec2f(2, 2), 	
	Vec2f(2, 3),	Vec2f(1, 1),
	Vec2f(4, 6)
};
static const int HPS[Enemy::TYPECT] = 
{
	1,
	2,	2,	1,
	2,	1,
	16
};
static const int DAMAGES[Enemy::TYPECT] = 
{
	0,
	1,	1,	1,
	1,	1,
	2
};
static const bool KAMIKAZES[Enemy::TYPECT] = 
{
	false,
	false,	false,	true,
	false,	true,
	false
};
static const bool RESPAWNS[Enemy::TYPECT] =
{
	false,
	true,	true,	true,	
	false,	false,
	false
};
static const int POINTS[Enemy::TYPECT] =
{
	0,
	100,	100,	150,
	200,	50,
	2000
};
static const unsigned DROPCHANCES[Enemy::TYPECT] = 
{
	100,
	25,	25, 	25,
	25,	0,
	100
};

int Enemy::TDamage(int t)
{
	return DAMAGES[t];
}

#include "player.hpp"
#include "weapon.hpp"

class Enemy::Type
{
public:
	virtual void Init(Enemy & en)
	{
	}

	virtual void Update(Enemy & en) 
	{
	}

	virtual void PlayerHit(Enemy & en, Player & plr)
	{
	}

	virtual void WeaponHit(Enemy & en, Weapon & wpn)
	{
	}

	virtual void Die(Enemy & en)
	{
		en.DieSmall();
		en.ItemDrop();
	}
};

class Enemy::ItemBox : public Enemy::Type
{
	void Update(Enemy & en)
	{
		en.frame += .125f;
	}
}
	ibox;

#include "stage.hpp"
#include <cmath>

class Enemy::Drifter : public Enemy::Type
{
public:
	void Update(Enemy & en)
	{
		const float FPU = .0625f;
		en.vel.x = FPU * en.dir;

		for (int ax = Y; ax > NOAXIS; --ax)
		{
			en.pos[ax] += en.vel[ax];

			en.stghit[ax] = Stg.AxisHit(en.body, en.pos, ax);
			if (en.StgHitting(ax))
				en.pos[ax] -= en.stghit[ax];
		}

		if (en.StgHitting(X))
		{
			if (std::abs(en.stghit.x) > 0)
				en.Face(-en.dir);
			else
				en.stghit.x = FLT_MAX;
		}

		en.frame += FPU;
	}
}
	drifter;

class Enemy::Walker : public Enemy::Type
{
public:
	void Update(Enemy & en)
	{
		drifter.Update(en);

		if (en.StgHitting(Y))
		{
			en.vel.y = 0;
		}
		else
		{
			const float DROP_ACCEL = 1.f/64;
			en.vel.y = std::min(en.vel.y + DROP_ACCEL, 1.f);
		}
	}
} 
	walker;

#include "math.hpp"

class Enemy::Flyer : public Enemy::Type
{
public:
	void Update(Enemy & en)
	{
		en.vel.x = .125f * en.dir;
		en.vel.y = .125f * Sin(en.frame * .125f);
		en.pos += en.vel;
		en.frame += .125f;
	}
} 
	flyer;

class Enemy::Tosser : public Enemy::Type
{
public:
	enum State
	{
		BOUNCE,
		AIM,
		THROW
	};

	enum
	{
		AIM_FRAME = 2,
		THROW_FRAME
	};

	void StateChange(Enemy & en, int s)
	{
		const float HOP_VEL0 = -.25f;

		en.Face(Plr.pos.x - en.pos.x);
		Cue shotcue (Cue::ENEMY + Enemy::ROCK);

		switch (s)
		{
		case THROW:
			en.frame = THROW_FRAME;
			Enmes.Init(en.pos.x - .5f*SIZES[Enemy::ROCK].x,
					en.pos.y, shotcue);
			break;

		case AIM:
			en.frame = AIM_FRAME;
			break;

		default:
			en.frame = (int)(en.frame + 1) % 2;
			en.vel.y = HOP_VEL0;
		}

		en.state = s;
	}

	void Update(Enemy & en)
	{
		const float HOP_ACCEL = 1.f/64;

		switch (en.state)
		{
			case AIM:
				if (en.frame >= THROW_FRAME)
					StateChange(en, THROW);
				en.vel = Vec2f(0, 0);
				break;

			case THROW:
				if (en.frame >= THROW_FRAME + 1)
					StateChange(en, BOUNCE);
				en.vel = Vec2f(0, 0);
				break;

			default:
				en.vel.x = .0625f * en.dir;
				en.vel.y += HOP_ACCEL;
		}

		for (int ax = Y; ax > NOAXIS; --ax)
		{
			en.pos[ax] += en.vel[ax];

			en.stghit[ax] = Stg.AxisHit(en.body, en.pos, ax);
			if (en.StgHitting(ax))
				en.pos[ax] -= en.stghit[ax];
		}

		float toplr = Plr.pos.x - en.pos.x;
	
		if (en.state == BOUNCE)
		{
			if (en.StgHitting(Y) && en.stghit.y >= 0)
			{
				if (std::abs(toplr) < 12)
					StateChange(en, AIM);
				else
				{
					StateChange(en, BOUNCE);
					if (en.StgHitting(X) && 
						std::abs(en.stghit.x) > 0)
					{
						en.vel.y *= 1.25f;
					}
				}
			}
		}
		else
		{
			en.frame += 1.f/16;
		}
	}
}
	tosser;

class Enemy::Rock : public Enemy::Type
{
public:
	void Update(Enemy & en)
	{
		en.vel.x = .5f * en.dir;
		en.vel.y += 1.f/64;

		for (int ax = Y; ax > NOAXIS; --ax)
		{
			en.pos[ax] += en.vel[ax];

			en.stghit[ax] = Stg.AxisHit(en.body, en.pos, ax);
			if (en.StgHitting(ax))
			{
				en.DieSmall();
				en.snd = NULL;
				en.Bury();
				break;
			}
		}
	}
}
	rock;

#include "music.hpp"

#include "item.hpp"

#include "effect.hpp"

#include "rand.hpp"

class Enemy::Boss1 : public Enemy::Type
{
public:
	enum State
	{
		IDLE,
		JUMP,
		DIE,
		STATECT
	};

	void Init(Enemy & en)
	{
		en.stun = -1;
		en.frame = 1;
		en.dropitem = Item::STGCLEAR;
	}

	void StateChange(Enemy & en, int s)
	{
		const float JUMP_VEL0 = -1.f;
		const float SPEEDX = .125f;

		float toplr = Plr.pos.x - en.pos.x;

		switch (s)
		{
		case JUMP:
			en.Face(toplr);
			en.vel.x = en.dir * SPEEDX;
			en.vel.y = JUMP_VEL0;
			if (std::abs(toplr) >= 8)
				en.vel.y *= .5f;
			break;

		case IDLE:
		case DIE:
			en.vel.x = en.vel.y = 0;
			break;

		default:
			;
		}

		en.state = s;
		en.frame = 0;
	}

	void Update(Enemy & en)
	{
		Cue fxcue (Effect::EN_KILLED);
		Vec2f fxpos;
		
		const float JUMP_ACCEL = 1.f/32;
		const float JUMP_TERMVEL = 1.f;
		const float FPU = 1.f/64;
		const float DIE_FPU = 1.f/128;

		switch (en.state)
		{
		case JUMP:
			en.vel.y = std::min(en.vel.y+JUMP_ACCEL, JUMP_TERMVEL);
			break;

		case IDLE:
			if (en.frame >= 1)
			{
				StateChange(en, JUMP);
			}
			break;

		case DIE:
			if (en.frame >= 1)
			{
				en.ItemDrop();
				en.Bury();
			}
			else if ((int)(en.frame * 64) > 
					(int)((en.frame - DIE_FPU) * 64))
			{
				Efx.Init(en.pos.x+en.body.off.x
						+Rand.Get(en.body.size.x),
					en.pos.y+en.body.off.y
						+Rand.Get(en.body.size.y), 
					fxcue);
			}
			break;
		default:
			;
		}

		for (int ax = Y; ax > NOAXIS; --ax)
		{
			en.pos[ax] += en.vel[ax];

			en.stghit[ax] = Stg.AxisHit(en.body, en.pos, ax);
			if (en.StgHitting(ax))
				en.pos[ax] -= en.stghit[ax];
		}

		switch (en.state)
		{
		case JUMP:
			if (en.StgHitting(Y) && en.stghit.y >= 0)
			{
				StateChange(en, IDLE);
			}
			break;

		case DIE:
			en.frame += DIE_FPU;
			en.alpha -= DIE_FPU;
			break;

		case IDLE:
			en.frame += FPU;
			break;

		default:
			;
		}
	}

	void Die(Enemy & en)
	{
		en.DieLarge();
		StateChange(en, DIE);
	}
}
	boss1;

static Enemy::Type *TYPES[Enemy::TYPECT] = 
{
	&ibox,
	&drifter,	&walker,	&flyer,
	&tosser,	&rock,
	&boss1
};

Enemy::Enemy()
{
	home = Vec2f(-1, -1);
	stun = 0;
	type = NULL;
	typen = NOTYPE;
	state = 0;
}

Enemy::~Enemy()
{
}

#include "image.hpp"
#include "sound.hpp"

static Image *imgs[Enemy::TYPECT];
static Sound *hitsnd;
static Sound *killsnd;
static Sound *boomsnd;

static const char *TYPESTRS[] =
{
	"ibox",
	"drifter",
	"walker",
	"flyer",
	"tosser",
	"rock",
	"boss1"
};

static const char *DIR = "enemy/";

bool Enemy::TOpen(Cue & cue)
{
	int tn = cue.TypeN(Cue::ENEMY);
	imgs[tn] = AllImgs.Get(std::string(DIR) + TYPESTRS[tn]);
	return true;
}

bool Enemy::Open()
{
	//AllImgs.BatchGet(imgs, TYPECT, DIR, TYPESTRS);

	hitsnd = AllSnds.Get(std::string(DIR) + "hit");
	killsnd = AllSnds.Get(std::string(DIR) + "kill");
	boomsnd = AllSnds.Get(std::string(DIR) + "boom");
	return true;
}

#include "cue.hpp"

void Enemy::Init(float x, float y, Cue & cue)
{
	typen = cue.TypeN(Cue::ENEMY);
	type = TYPES[typen];
	
	state = 0;

	body.size = SIZES[typen];
	body.off = -SIZES[typen] * .5f;
	home = Vec2f(x, y);

	pos = home; 
	Face(Plr.pos.x - x);
	hp = HPS[typen];

	stun = 0;
	dropitem = ItemScan();

	img = imgs[typen];

	Cues.Erase(x, y);

	if (type)
		type->Init(*this);
}

#include "video.hpp"

void Enemy::Update()
{
	if (stun > 0)
		--stun;

	if (Vulnerable())
	{
		if (type)
		{
			type->Update(*this);
		}
	}

	if ((!Live() && typen < BOSS1) || Gone())
	{
		Bury();
		if (Gone() || RESPAWNS[typen])
			CueReturn();
	}
}

void Enemy::Hit(Player & plr)
{
	if (type && Vulnerable() && plr.Vulnerable() && DAMAGES[typen])
	{
		if (KAMIKAZES[typen])
		{
			Kill();
		}
		else
			type->PlayerHit(*this, plr);
	}
}

#include "game.hpp"

#include "rand.hpp"

#include "effect.hpp"

void Enemy::Hit(Weapon & wpn)
{
	if (!Vulnerable())
		return;

	hp -= wpn.Damage();
	stun = 16;
	snd = hitsnd;

	Vec2f fxpos (body.Center());
	fxpos += (wpn.pos - pos) * .5f;
	fxpos = fxpos.LowerBound(body.LeftTop());
	fxpos = fxpos.UpperBound(body.RightBot());
	Cue fxcue (Effect::EN_HIT);
	Efx.Init(pos.x + fxpos.x, pos.y + fxpos.y, fxcue);
	
	if (!Live())
	{
		Kill();
		if (type)
			type->Die(*this);

		Game.Score(POINTS[typen]);
	}
}

#include "hud.hpp"

void Enemy::HUDUpdate()
{
	if (stun >= 0)
	{
		HUD.Put(HUD.GAUGE_BHP, hp);
		HUD.IconSet(HUD.ICON_BOSS, typen - BOSS1);
	}
}

void Enemy::DieSmall()
{
	Cue fxcue (Effect::EN_KILLED);
	Efx.Init(pos.x + body.AxCenter(X), pos.y + body.AxCenter(Y), fxcue);
	snd = killsnd;
}

void Enemy::DieLarge()
{
	Cue fxcue (Effect::EN_KILLED);
	Efx.Init(pos.x + body.AxCenter(X), pos.y + body.AxCenter(Y), fxcue);
	snd = boomsnd;
}

int Enemy::ItemScan()
{
	Vec2i lt (pos + body.LeftTop());
	Vec2i rb (pos + body.RightBot());

	for (int r = lt.y; r <= rb.y; ++r)
	{
		for (int c = lt.x; c <= rb.x; ++c)
		{
			Cue icue (Cues.Get(c, r));

			if (!icue.IsItem())
				continue;

			if (Item::TSecret(icue.TypeN(Cue::ITEM)))
				continue;

			return icue.TypeN(Cue::ITEM);
		}
	}

	return Item::NOTYPE;
}

void Enemy::ItemDrop()
{
	if (dropitem == Item::NOTYPE 
			&& Rand.Get(100) < DROPCHANCES[typen])
	{
		dropitem = ItemScan();
		if (dropitem == Item::NOTYPE)
		{
			if (Plr.MeleeUpEarned())
				dropitem = Item::MELEE;
			else
				dropitem = Item::AMMO_S;
		}
	}

	if (dropitem > Item::NOTYPE)
	{
		Cue icue (dropitem + Cue::ITEM);
		Itms.Init(pos.x + body.AxCenter(X), 
				pos.y + body.AxCenter(Y), icue);
	}
}

void Enemy::CueReturn()
{
	Cues.Put(home.x, home.y, Cue(typen + Cue::ENEMY));
}
